package vip.xiaonuo.device.modular.service.impl;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.common.cache.CommonCacheOperator;
import vip.xiaonuo.common.util.SignTool;
import vip.xiaonuo.common.util.WashDataUtil;
import vip.xiaonuo.device.core.enums.Constants;
import vip.xiaonuo.device.core.enums.DeviceErrEnum;
import vip.xiaonuo.device.modular.entity.ApiLog;
import vip.xiaonuo.device.modular.mapper.ApiLogMapper;
import vip.xiaonuo.device.modular.mapper.CabinetInfoMapper;
import vip.xiaonuo.device.modular.service.DeviceApiService;
import vip.xiaonuo.device.modular.service.TokenService;
import vip.xiaonuo.device.vo.Result;

import java.util.Date;

@ConfigurationProperties(prefix = "remote")
@Slf4j
@Transactional
public abstract class DeviceBaseService implements DeviceApiService {

    @Setter
    private String iotUrl;
    @Setter
    private String iotToken;

    @Autowired
    private CabinetInfoMapper cabinetInfoMapper;
    @Autowired
    protected ApiLogMapper apiLogMapper;
    @Autowired
    private TokenService tokenService;
    @Autowired
    private CommonCacheOperator commonCacheOperator;

    @Override
    public Result execute(String jsonStr) {
        try {
            log.info("jsonStr={}", jsonStr);
            // *****This is very important, keep the original order for md5 verify.*****
            JSONObject params = JSONObject.parseObject(jsonStr, Feature.OrderedField);
            DeviceErrEnum errEnum = check(params);
//             DeviceErrEnum errEnum = DeviceErrEnum.SUCCESS;
            if (errEnum == DeviceErrEnum.SUCCESS) {
                //数据校验
                Result dataCheck = checkData(params);
                if (dataCheck.isSucc()) {
                    Result result = handle(params);
                    if (getLogFlag()) {
                        saveLog(params, result, errEnum);
                    }
                    return result;
                } else {
                    return dataCheck;
                }
            } else {
                return Result.fail(errEnum.getInfo());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return Result.fail("系统异常," + e.getMessage());
        }
    }

    @Override
    public void execute(JSONObject params, String parentId) {
        DeviceErrEnum errEnum = DeviceErrEnum.SUCCESS;
        saveLog(params, errEnum, parentId);
    }

    public void saveLog(JSONObject params, DeviceErrEnum errEnum, String parentId) {
        Date reqTime = params.getDate("timestamp");
        ApiLog log = new ApiLog();
        log.setEventTime(reqTime);
        log.setCreateTime(new Date());
        log.setReqType(getReqType());
        log.setReqJson(params.toJSONString());
//        log.setRespJson(result.toJsonStr());
        log.setResulltType(errEnum.getValue());
        log.setErrorMsg(errEnum.getInfo());
        log.setParentId(parentId);
        handleLog(log, params);
        apiLogMapper.insert(log);
    }

    private DeviceErrEnum check(JSONObject params) {
        Date reqTime = params.getDate("timestamp");
        long minDiff = DateUtil.between(reqTime, new Date(), DateUnit.MINUTE);
        if (minDiff > 2L) return DeviceErrEnum.REQ_TIMEOUT; //超出时间戳2分钟的数据记录不做处理，防重发报文攻击
        if (minDiff < 0L) return DeviceErrEnum.REQ_OVERTIME;  //小于此时间戳的报文应该不予处理并记录异常
        if (StringUtils.isEmpty(this.getToken())) return DeviceErrEnum.SIGN_CHECK_FAILED;
        // 0-签名校验成功, 4-签名校验失败
        if (!SignTool.verifyMd5Hex16(params, this.getToken())) return DeviceErrEnum.SIGN_CHECK_FAILED;
        return DeviceErrEnum.SUCCESS;
    }

    private void saveLog(JSONObject params, Result result, DeviceErrEnum errEnum) {
        Date offlineReqTime = params.getDate("offlinetimestamp");
        ApiLog apiLog = new ApiLog();
        if (offlineReqTime != null) { // 如果报文中包含offlinetimestamp那就是离线请求
            apiLog.setOnlineFlag(false);
            apiLog.setEventTime(offlineReqTime);
        } else {
            Date reqTime = params.getDate("timestamp");
            apiLog.setOnlineFlag(true);
            apiLog.setEventTime(reqTime);
        }
        apiLog.setCreateTime(new Date());
        apiLog.setReqType(getReqType());
        apiLog.setReqJson(params.toJSONString());
        apiLog.setRespJson(result.toJsonStr());
        apiLog.setResulltType(errEnum.getValue());
        apiLog.setErrorMsg(errEnum.getInfo());
        handleLog(apiLog, params);
        apiLogMapper.insert(apiLog);
        handleWithParentLog(apiLog, params);

        if (ObjectUtil.equal(getReqType(), Constants.OPENDOOR)
                || ObjectUtil.equal(getReqType(), Constants.CLOSEDOOR)) {
            //回调
            tokenService.handleDoorCallBack(apiLog);
            //更新上一条记录的nextId
            if (StringUtils.isNotBlank(apiLog.getRelationId())) {
                apiLogMapper.updateNextId(apiLog.getRelationId(), apiLog.getId());
            }
        }
        if (!ObjectUtil.equal(getReqType(), Constants.GETUSERINFO) && !ObjectUtil.equal(getReqType(), Constants.GETDOORAUTH) && !ObjectUtil.equal(getReqType(), Constants.GETDOORAUTHLIST) && !ObjectUtil.equal(getReqType(),
                Constants.WEBHOOK) && !ObjectUtil.equal(getReqType(), Constants.CHECKUSER)) {
            Object flag = commonCacheOperator.get(WashDataUtil.WASH_KEY);
            if (flag == null) {
                commonCacheOperator.put(WashDataUtil.WASH_KEY,1);
            } else {
                commonCacheOperator.put(WashDataUtil.WASH_KEY, Integer.valueOf(flag.toString()) + 1);
            }
        }
    }

    protected abstract Integer getReqType();

    protected Boolean getLogFlag() {
        return true;
    }

    private String getToken() {
        return tokenService.getToken();
    }

    protected Result handle(JSONObject param) {
        return Result.ok();
        // do nothing. if you have specific requirements, overwrite it.
    }

    protected void handleLog(ApiLog log, JSONObject param) {
        // do nothing. if you have specific requirements, overwrite it.
    }

    protected void handleWithParentLog(ApiLog log, JSONObject params) {
        // do nothing. if you have specific requirements, overwrite it.
    }

    protected Result checkData(JSONObject param) {
        return Result.ok();
        // do nothing. if you have specific requirements, overwrite it.
    }
}
